
#include "owl_list.h"
#include <stdarg.h>
#include <stdlib.h>
#include "util.h"

#define SHELL_CMDLEN 45
#define SHELL_ARGS_MAX 3
#define SHELL_PRINTF_LEN 45

/* Internal command representation */
struct shell_icmd {
        const struct shell_cmd *cmd;
        void *ctx;
        const char *instance;
        struct list_head list;
};

struct shell_ctx {
        void *data;                             /* context, passed to cmd_f */
        struct list_head icmds;
        const char *prompt;                     /* prompt */
        int (*write_f)(void *ctx, const void *data, int len);
        void *ctx;                              /* passed to write_f */
    
        char *buf;
        int buflen;
        int curpos;
        int escape;
        
        int argc;
        const char *argv[SHELL_ARGS_MAX];
        const char *instance; /* current instance of command */

        const struct shell_cmd *defcmd;
        void *defctx;
};

//static void parse_line(char *line, int *argc, const char **argv);
//static struct shell_icmd *shell_find_cmd(struct shell_ctx *priv, const char *label, const char *instance);

void
shell_destroy(struct shell_ctx *priv)
{
        struct list_head *pos, *tmp;

        if (priv == NULL)
                return;

        vos_free(priv->buf);

        //list_for_each_safe(pos, tmp, &priv->icmds) {
        //        struct shell_icmd *icmd;
        //        icmd = list_entry(pos, struct shell_icmd, list);
        //        list_del(&icmd->list);
        //        vos_free(icmd);
        //}
        vos_free(priv);
}

struct shell_ctx * 
shell_create(const char *prompt, int (*write_f)(void *ctx, const void *data, int len), void *ctx) {

        struct shell_ctx *priv;
        
        if (write_f == NULL)
                return NULL;
        
        priv = vos_malloc(sizeof(struct shell_ctx));

        if (priv == NULL)
                return NULL;

        vos_memset(priv, 0, sizeof(struct shell_ctx));
        
        priv->prompt = prompt ? prompt : "$";

        priv->buflen = SHELL_CMDLEN;
        priv->buf = vos_malloc(priv->buflen);
		
        if (priv->buf == NULL) {
                shell_destroy(priv);
                return NULL;
        }
        vos_memset(priv->buf, 0, priv->buflen);

        priv->write_f = write_f;
        priv->ctx = ctx;

        list_init(&priv->icmds);
        
        return priv;
}

int
shell_addcmd(struct shell_ctx *priv, const struct shell_cmd *cmd, void *ctx) {
        return shell_addcmd_ext(priv, cmd, ctx, NULL);
}

int
shell_addcmd_ext(struct shell_ctx *priv, const struct shell_cmd *cmd,
                 void *ctx, const char *instance) {

		struct shell_icmd *icmd;

        if ((icmd = vos_alloc(sizeof(*icmd))) == NULL) {
                //owl_err("could not alloc icmd");
                //return OWL_ERR_MEM;
				return 3;
        }
        
        icmd->cmd = cmd;
        icmd->ctx = ctx;
        icmd->instance = instance;
        
        list_add_tail(&icmd->list, &priv->icmds);
        return 0;
}

void
shell_defcmd(struct shell_ctx *priv, const struct shell_cmd *cmd, void *ctx)
{
        priv->defcmd = cmd;
        priv->defctx = ctx;
}

void
shell_key(struct shell_ctx *priv, char key)
{
        struct shell_icmd *icmd = NULL;

        switch (key) {

        case '\b':
                if (priv->curpos == 0)
                        break;

                owlsh_printf(priv, "\b \b");
                priv->curpos--;
                break;

        case '\r':
        case '\n':
                priv->buf[priv->curpos] = '\0';

                if (!(priv->flags & OWLSH_NOECHO))
                        owlsh_printf(priv, "\r\n");

                owl_hexdump("SHELL RX", priv->buf, priv->curpos);
                parse_line(priv->buf, &priv->argc, priv->argv);
                icmd = owlsh_find_cmd(priv, priv->argv[0], priv->argv[1]);
                if (icmd != NULL) {
                        owl_info("invoking cmd, buf:'%s'", priv->buf);
                        priv->data = icmd->ctx;
                        /* Command matched on instance */
                        if (priv->argv[1] && icmd->instance &&
                            !owl_os_strcmp(priv->argv[1], icmd->instance)) {
                                priv->instance = icmd->instance;
                                priv->argv[1] = priv->argv[0];
                                priv->argc--;
                                icmd->cmd->cmd_f(priv, priv->argc,
                                                 &priv->argv[1]);
                        } else {
                                priv->instance = NULL;
                                icmd->cmd->cmd_f(priv, priv->argc, priv->argv);
                        }
                } else if (priv->defcmd) {
                        owl_info("invoking defcmd");
                        priv->data = priv->defctx;
                        priv->defcmd->cmd_f(priv, priv->argc, priv->argv);
                } else if (owl_os_strlen(priv->argv[0])) {
                        owl_info("unknown cmd, buf:'%s'", priv->buf);
                        if (owlsh_compact(priv))
                                owlsh_perr(priv, OWL_ERR_USAGE);
                        else
                                owlsh_printf(priv, "command not found:%s\r\n",
                                             priv->argv[0]);
                }

                priv->curpos = 0;

                if (!(priv->flags & OWLSH_NOPROMPT))
                        owlsh_printf(priv, "%s ", priv->prompt);
                break;

        default:
                /* save one char for the null termination */
                if (priv->curpos >= OWL_CONFIG_SHELL_CMDLEN - 1)
                        break;
                
                /* only store visible chars */
                if (key >= ' ' && key < 0x7f) {
                        if (!(priv->flags & OWLSH_NOECHO))
                                owlsh_printf(priv, "%c", key);
                        priv->buf[priv->curpos] = key;
                        priv->curpos++;
                }

                break;
        }
}

int
shell_printf(struct owlsh_ctx *priv, const char *fmt, ...)
{
        char *str = NULL;
        va_list args;
        int len;

        va_start(args, fmt);

        if ((str = vos_malloc(SHELL_PRINTF_LEN)) == NULL)
                return 3;
				//return OWL_ERR_MEM;
        
        if ((len = vsnprintf(
                     str, SHELL_PRINTF_LEN, fmt, args)) < 0) {
                vos_free(str);
                return 3;
				//return OWL_ERR_MEM;
        }

        //owl_hexdump("SHELL TX", str, len);
        len = priv->write_f(priv->ctx, str, len);
        vos_free(str);
        return len;
}